Skip to main content

05 原型链:V8 实现对象继承

继承就是一个对象可以访问另外一个对象中的属性和方法:

语言实现继承的方式

基于类的设计

C++、Java、C#:提供了非常复杂的规则,并提供了非常多的关键字,诸如 class、friend、protected、private、interface 等,通过组合使用这些关键字,就可以实现继承。

如果业务复杂,需要创建大量的对象,需要维护非常复杂的继承关系,会导致代码过度复杂和臃肿。

基于原型继承的设计

JavaScript:JavaScript 本身不提供一个 class 实现,仅在对象中引入了一个原型的属性,就实现了语言的继承机制,基于原型的继承省去了很多基于类继承时的繁文缛节,简洁而优美。

原型继承是如何实现的

JavaScript 的每个对象都包含了一个隐藏属性 proto ,该隐藏属性 proto 称之为该对象的原型 (prototype),proto 指向了内存中的另外一个对象,把 proto 指向的对象称为该对象的原型对象,该对象就可以直接访问其原型对象的方法或者属性。

让 C 对象的原型指向 B 对象,那么便可以利用 C 对象来直接访问 B 对象中的属性或者方法了:

当 C 对象将它的 proto 属性指向了 B 对象后,那么通过对象 C 来访问对象 B 中的 name 属性时,V8 会先从对象 C 中查找,但是并没有查找到,接下来 V8 继续在其原型对象 B 中查找,因为对象 B 中包含了 name 属性,那么 V8 就直接返回对象 B 中的 name 属性值,虽然 C 和 B 是两个不同的对象,但是使用的时候,B 的属性看上去就像是 C 的属性一样。

同样的方式,B 也是一个对象,它也有自己的 proto 属性,比如它的属性指向了内存中另外一块对象 A:

把这个查找属性的路径称为原型链,它像一个链条一样,将几个原型链接了起来。

作用域链是沿着函数的作用域一级一级来查找变量的,而原型链是沿着对象的原型一级一级来查找属性的,虽然它们的实现方式是类似的,但是它们的用途是不同的。

如果有另外一个对象 D,它可以和 C 共同拥有同一个原型对象 B:

对象 C 和对象 D 的原型都指向了对象 B,它们共同拥有同一个原型对象,通过 D 去访问 name 属性或者 color 属性时,返回的值和使用对象 C 访问 name 属性和 color 属性是一样的,因为它们是同一个数据。

继承就是一个对象可以访问另外一个对象中的属性和方法,在 JavaScript 中,通过原型和原型链的方式来实现了继承特性。

实践:利用 proto 实现继承

var animal = {
type: "Default",
color: "Default",
getInfo: function () {
return `Type is: ${this.type},color is ${this.color}.`;
},
};
var dog = {
type: "Dog",
color: "Black",
};

通过设置 dog 对象中的 proto 属性,将其指向 animal:

dog.__proto__ = animal

现在就可以使用 dog 来调用 animal 中的 getInfo 方法了:

dog.getInfo()

构造函数是怎么创建对象的

要创建一个 dog 对象,先创建一个 DogFactory 的函数,属性通过参数进行传递,在函数体内,通过 this 设置属性值。

function DogFactory(type, color) {
this.type = type;
this.color = color;
}

结合关键字 new 创建对象。

var dog = new DogFactory('Dog','Black')

后面的函数称为构造函数,因为通过执行 new 配合一个函数,JavaScript 虚拟机便会返回一个对象。

当 V8 执行上面这段代码时,V8 会在背后悄悄地做了以下几件事情,模拟代码如下所示:

var dog = {};
dog.__proto__ = DogFactory.prototype;
DogFactory.call(dog, "Dog", "Black");

  1. 创建一个空白对象 dog;
  2. 将 DogFactory 的 prototype 属性设置为 dog 的原型对象;
  3. 再使用 dog 来调用 DogFactory,这时候 DogFactory 函数中的 this 就指向了对象 dog,然后在 DogFactory 函数中,利用 this 对对象 dog 执行属性填充操作,最终就创建了对象 dog。

构造函数怎么实现继承

function DogFactory(type, color) {
this.type = type;
this.color = color;
this.constant_temperature = 1;
}
var dog1 = new DogFactory("Dog", "Black");
var dog2 = new DogFactory("Dog", "Black");
var dog3 = new DogFactory("Dog", "Black");

对象 dog1 到 dog3 中的 constant_temperature 属性都占用了一块空间,但是这是一个通用的属性,没有必要在每个对象中都为该属性分配一块空间,可以将该属性设置公用的。

函数除了 name 和 code 两个隐藏属性,还有另外一个隐藏属性 prototype。每个函数对象中都有一个公开的 prototype 属性,将函数作为构造函数来创建一个新的对象时,新创建对象的原型对象就指向了该函数的 prototype 属性。如果只是正常调用该函数,那么 prototype 属性将不起作用。

function DogFactory(type, color) {
this.type = type;
this.color = color;
}
DogFactory.prototype.constant_temperature = 1;
var dog1 = new DogFactory("Dog", "Black");
var dog2 = new DogFactory("Dog", "Black");
var dog3 = new DogFactory("Dog", "Black");